/*
** help.c
**
** Pictor, Version 1.51, Copyright (c) 1992-94 SoftCircuits
** Redistributed by permission.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "compress.h"
#include "pictor.h"

/* Update for WATCOM- Converted all instances of getw() to _getw()- W. Jones. */
#ifdef __WATCOMC__
	#define getw _getw
#endif


#define WINROWS   19    /* help window dimensions */
#define WINCOLS   74

static char signature[] = "PICTORHLP00";
static char *title = "Help";

static NODE *root_node = NULL;
static long *help_array = NULL;
static char **topics_array = NULL;
static char *topics_buff = NULL;
static char *comp_buff = NULL,*uncomp_buff = NULL;
static unsigned num_topics,max_comp,max_uncomp;
static unsigned topics_comp,topics_uncomp;

static char *hlpfile;
static COLORSTRUCT *hlpcolors,*msgcolors;

static char buffer[128];
static int helpready = FALSE;

#define UPDATE_NOUPDATE 0
#define UPDATE_LINKSEL  1
#define UPDATE_POSITION 2
#define UPDATE_NEWTOPIC 3

static unsigned uncomp_size,comp_size;

#define STACK_SIZE   30
static int stack_count = 0;
static int topic_stack[STACK_SIZE];

typedef struct _LINK {
	int row,col;
	unsigned pos;
	int len;
} LINK;

#define LINK_CHAR    '|'
#define MAX_LINKS    30

static LINK link_array[MAX_LINKS];
static int curr_link,num_links;


/*
** Lets the user select a help topic from the help index.
*/
static int helpindex(int *topic)
{
	extern int _PL_lbcols,_PL_lbcolwidth;
	int result,old_lbcols,old_lbcolwidth;

	old_lbcols = _PL_lbcols;
	old_lbcolwidth = _PL_lbcolwidth;
	_PL_lbcols = 3;
	_PL_lbcolwidth = 23;

	pushstatus();
	result = listbox(topics_array,num_topics,topic,"Help Index",msgcolors);
	popstatus();

	_PL_lbcols = old_lbcols;
	_PL_lbcolwidth = old_lbcolwidth;

	return(result);

} /* helpindex */

/*
** Compare routine used by bsearch().
*/
static int compare(const void *item1,const void *item2)
{
	return(stricmp(*(char **)item1,*(char **)item2));

} /* compare */

/*
** Searches the current topic array for the given topic.
*/
static int lookup_topic(char *topic,int *curr_topic)
{
	char **ptr;

	if(topic == NULL) {
		return(helpindex(curr_topic));
	}
	ptr = bsearch(&topic,topics_array,num_topics,sizeof(char *),compare);
	if(ptr == NULL) {
		sprintf(buffer,"No help available for:\n\"%s\"",topic);
		messagebox(buffer,title,MB_OK,msgcolors);
		return(FALSE);
	}
	*curr_topic = (ptr - topics_array);

	return(TRUE);

} /* lookup_topic */

/*
** Saves a topic onto the topic stack. If the stack is full, the
** topics are scrolled so that the least recently push topic is
** removed to make room.
*/
void push_topic(int topic)
{
	int i;

	/* don't save topic if same as last one */
	if(stack_count && topic_stack[stack_count - 1] == topic)
		return;

	if(stack_count >= STACK_SIZE) {
		stack_count--;
		for(i = 0;i < stack_count;i++)
			topic_stack[i] = topic_stack[i + 1];
	}
	topic_stack[stack_count++] = topic;

} /* push_topic */

/*
** If the current topic is not the only topic on the stack,
** the current topic is discarded and the previous one is returned.
*/
int pop_topic(int *topic)
{
	/* must have at least 1 topic besides current one */
	if(stack_count > 1) {
		stack_count -= 2;
		*topic = topic_stack[stack_count];
		return(TRUE);
	}
	return(FALSE);

} /* pop_topic */

/*
** Scan foreward the specified number of lines.
*/
static int scanforeward(unsigned *pos,int lines)
{
	if(*pos >= uncomp_size) {
		beep();
		return(UPDATE_NOUPDATE);
	}
	while(lines && *pos < uncomp_size) {
		while(uncomp_buff[*pos] != '\n' && *pos < uncomp_size)
			(*pos)++;
		if(*pos < uncomp_size)
			(*pos)++;
		lines--;
	}
	return(UPDATE_POSITION);

} /* scanforeward */

/*
** Scans backward the number of specified lines.
*/
static int scanbackward(unsigned *pos,int lines)
{
	if(*pos == 0) {
		beep();
		return(UPDATE_NOUPDATE);
	}
	if(*pos == 1) {
		*pos = 0;
		return(UPDATE_POSITION);
	}
	(*pos)--;
	while(lines && *pos > 0) {
		do {
			(*pos)--;
		} while(uncomp_buff[*pos] != '\n' && *pos > 0);
		lines--;
	}
	if(uncomp_buff[*pos] == '\n')
		(*pos)++;
	return(UPDATE_POSITION);

} /* scanbackward */

/*
** Main help routines.
*/
static int _helprun(char *topic,FILE *stream)
{
	int row,in_link,update = UPDATE_NEWTOPIC,curr_topic = 0;
	unsigned i,done = FALSE,help_pos = 0,old_pos = 0;
	char *ptr;

	if(lookup_topic(topic,&curr_topic) == FALSE)
		return(FALSE);

	i = wopen(center(WINROWS,_PL_rows),center(WINCOLS,_PL_columns),
		WINROWS,WINCOLS,hlpcolors->normal,WO_SHADOW);
	if(i == FALSE) {
		messagebox("Insufficient memory",title,MB_OK,msgcolors);
		return(FALSE);
	}
	
	pushstatus();
	statusbar("<F1>=Index  <Tab>=Select  <Enter>=View  <Backspace>=Previous"  
		"  <Escape>=Done");

	while(!done) {
		if(update != UPDATE_NOUPDATE) {
			if(update == UPDATE_NEWTOPIC) {
				fseek(stream,help_array[curr_topic],SEEK_SET);
				comp_size = getw(stream);
				uncomp_size = getw(stream);

				/* Note: in the unlikey event that the disk is removed */
				/* here, it is possible that fseek will set an EOF */
				/* condition instead of an error. So we will check for */
				/* both. */

				if(!ferror(stream) && !feof(stream))
					fread(comp_buff,sizeof(char),comp_size,stream);

				if(ferror(stream) || feof(stream)) {
					messagebox("Error reading help file",title,MB_OK,msgcolors);
					clearerr(stream);
					update = UPDATE_NOUPDATE;
				}
				else {
					uncompress((BYTE *)comp_buff,uncomp_size,
						(BYTE *)uncomp_buff,root_node);
					xprintf(wtitle,"Help: %s",topics_array[curr_topic]);

					push_topic(curr_topic);
					num_links = old_pos = help_pos = 0;
					update = UPDATE_POSITION;
				}
			}
			
			if(update == UPDATE_POSITION) {
				if(num_links) 
					old_pos = link_array[curr_link].pos;
				num_links = 0;
				
				/* display help text */
				for(i = help_pos,row = 1;row <= (WINROWS - 2);row++) {
					in_link = FALSE;
					setwpos(row,1);
					while(i < uncomp_size && uncomp_buff[i] != '\n') {
						if(uncomp_buff[i] == LINK_CHAR && num_links < MAX_LINKS) {
							if(in_link == FALSE) {
								link_array[num_links].row = row;
								link_array[num_links].col = getwcol();
								link_array[num_links].pos = (i + 1);
								in_link = TRUE;
							}
							else {
								link_array[num_links].len = (i - 
									link_array[num_links].pos);
								num_links++;
								in_link = FALSE;
							}
						}
						else wputc(uncomp_buff[i]);
						i++;
					}
					wcleareol();
					i++;
				}

				/* determine active link */
				if(num_links > 0) {
					curr_link = -1;
					/* make previous link active if still visible */
					for(i = 0;i < (unsigned)num_links;i++) {
						if(link_array[i].pos == old_pos) {
							curr_link = i;
							break;
						}
					}
					/* else determine new active link */
					if(curr_link == -1) {
						if(link_array[0].pos > old_pos)
							curr_link = 0;
						else
							curr_link = (num_links - 1);
					}
				}
				update = UPDATE_LINKSEL;
			}
			if(update == UPDATE_LINKSEL) {
				for(i = 0;i < (unsigned)num_links;i++) {
					setwpos(link_array[i].row,link_array[i].col);
					if(i == (unsigned)curr_link)
						wrepa(hlpcolors->select,link_array[i].len);
					else
						wrepa(hlpcolors->boldnormal,link_array[i].len);
				}
				update = UPDATE_NOUPDATE;
			}
		}

		switch(kbdread()) {
			case BACKSPACE_KEY:
				/* pop previous topic off stack */
				if(pop_topic(&curr_topic))
					update = UPDATE_NEWTOPIC;
				else
					beep();
				break;
			case ENTER_KEY:
				if(num_links) {
					ptr = uncomp_buff + link_array[curr_link].pos;
					ptr[link_array[curr_link].len] = '\0';
					if(lookup_topic(ptr,&curr_topic))
						update = UPDATE_NEWTOPIC;
					ptr[link_array[curr_link].len] = LINK_CHAR;
				}
				else beep();
				break;
			case ESCAPE_KEY:
				done = TRUE;
				break;
			case TAB_KEY:
				if(num_links > 0) {
					if(++curr_link > (num_links - 1))
						curr_link = 0;
					update = UPDATE_LINKSEL;
				}
				else beep();
				break;
			case SHIFTTAB_KEY:
				if(num_links > 0) {
					if(--curr_link < 0) {
						curr_link = (num_links - 1);
					}
					update = UPDATE_LINKSEL;
				}
				else beep();
				break;
			case UP_KEY:
				update = scanbackward(&help_pos,1);
				break;
			case DOWN_KEY:
				update = scanforeward(&help_pos,1);
				break;
			case PGUP_KEY:
				update = scanbackward(&help_pos,WINROWS - 2);
				break;
			case PGDN_KEY:
				update = scanforeward(&help_pos,WINROWS - 2);
				break;
			case HOME_KEY:
				if(help_pos > 0) {
					help_pos = 0;
					update = UPDATE_POSITION;
				}
				break;
			case END_KEY:
				help_pos = uncomp_size;
				scanbackward(&help_pos,WINROWS - 3);
				update = UPDATE_POSITION;
				break;
			case F1_KEY:
				if(helpindex(&curr_topic))
					update = UPDATE_NEWTOPIC;
				break;
			default:
				beep();
				break;
		}
	}
	wclose();
	popstatus();

	return(TRUE);

} /* _helprun */

/*
** Destroys all help data structures and frees memory.
*/
static void free_helpmem(void)
{
	if(root_node) {
		freetree(root_node);
		root_node = NULL;
	}
	if(help_array) {
		free(help_array);
		help_array = NULL;
	}
	if(topics_array) {
		free(topics_array);
		topics_array = NULL;
	}
	if(topics_buff) {
		free(topics_buff);
		topics_buff = NULL;
	}
	if(comp_buff) {
		free(comp_buff);
		comp_buff = NULL;
	}
	if(uncomp_buff) {
		free(uncomp_buff);
		uncomp_buff = NULL;
	}

	helpready = FALSE;

} /* free_helpmem */

/*
** Allocates help structures. Returns TRUE and sets helpready = TRUE
** if successful. Returns FALSE if error.
*/
static int buildhelp(FILE *stream,int *mem_err)
{
	unsigned i;
	long fpos;
	char *ptr;

	if(helpready == TRUE)      /* help already allocated */
		free_helpmem();

	*mem_err = FALSE;

	/* check for valid help file signature */
	fread(buffer,sizeof(char),sizeof(signature),stream);
	if(strcmp(signature,buffer)) {
		sprintf(buffer,"Unrecognized help file format:\n\"%s\"",hlpfile);
		messagebox(buffer,title,MB_OK,msgcolors);
		return(FALSE);
	}

	*mem_err = TRUE;

	/* read and uncompress code tree */
	ptr = malloc(i = getw(stream));
	if(ptr == NULL)
		return(FALSE);
	fread(ptr,sizeof(char),i,stream);
	root_node = readtree((BYTE *)ptr);
	free(ptr);
	if(root_node == NULL)
		return(FALSE);

	/* skip over compressed help text */
	fread(&fpos,sizeof(long),1,stream);
	fseek(stream,fpos,SEEK_SET);

	num_topics = getw(stream);    /* number of help topics */
	max_comp = getw(stream);      /* largest compressed topic */
	max_uncomp = getw(stream);    /* largest uncompressed topic */

	/* read array of file offsets into help text */
	help_array = malloc(num_topics * sizeof(long));
	if(help_array == NULL)
		return(FALSE);
	fread(help_array,sizeof(long),num_topics,stream);

	topics_comp = getw(stream);   /* compressed topics size */
	topics_uncomp = getw(stream); /* uncompressed topics size */

	/* read and uncompress topic labels */
	ptr = malloc(topics_comp);
	topics_buff = malloc(topics_uncomp);
	if(ptr == NULL || topics_buff == NULL) {
		free(ptr);
		return(FALSE);
	}
	fread(ptr,sizeof(char),topics_comp,stream);
	uncompress((BYTE *)ptr,topics_uncomp,(BYTE *)topics_buff,root_node);
	free(ptr);

	/* build array of pointers into help topic labels */
	topics_array = malloc(num_topics * sizeof(char *));
	if(topics_array == NULL)
		return(FALSE);
	for(ptr = topics_buff,i = 0;i < num_topics;i++) {
		topics_array[i] = ptr;
		ptr += (strlen(ptr) + 1);
	}

	/* allocate buffers for help text */
	comp_buff = malloc(max_comp);
	uncomp_buff = malloc(max_uncomp);
	if(comp_buff == NULL || uncomp_buff == NULL)
		return(FALSE);

	*mem_err = FALSE;
	helpready = TRUE;
	return(TRUE);

} /* buildhelp */

/*
** This is the callable portion of runhelp that "wraps" _runhelp.
*/
int helprun(char *topic)
{
	static int in_help = FALSE;
	int result,mem_err;
	FILE *stream;

	if(in_help || _PL_helpfunc == NULL) {
		beep();
		return(FALSE);
	}

	in_help = TRUE;
	pushstatus();

	/* open help file */
	if((stream = fopen(hlpfile,"rb")) == NULL) {
		sprintf(buffer,"Help file not found:\n\"%s\"",hlpfile);
		messagebox(buffer,title,MB_OK,msgcolors);
		popstatus();
		in_help = FALSE;
		return(FALSE);
	}

	/* build help data structures if needed */
	if(helpready == FALSE) {
		if(!buildhelp(stream,&mem_err)) {
			if(mem_err)
				messagebox("Insufficient memory",title,MB_OK,msgcolors);
			fclose(stream);
			free_helpmem();
			popstatus();
			in_help = FALSE;
			return(FALSE);
		}
	}

	result = _helprun(topic,stream);

	fclose(stream);
	popstatus();
	in_help = FALSE;
	
	return(result);

} /* helprun */

/*
** Frees help memory and makes help inactive.
*/
void helpclose(void)
{
	free_helpmem();
	_PL_helpfunc = NULL;

} /* helpclose */

/*
** Defines help parameters and activates the help system.
*/
void helpopen(char *filename,COLORSTRUCT *hcolors,COLORSTRUCT *mcolors)
{
	/* make sure help is not active */
	if(_PL_helpfunc != NULL)
		helpclose();

	hlpfile = filename;
	hlpcolors = hcolors;
	msgcolors = mcolors;

	_PL_helpfunc = helprun;

	stack_count = 0;   /* clear topic history */

} /* helpopen */
